.set_source_id(id.source_id().clone())
}
+ /// Returns whether this is a "locked" dependency, basically whether it has
+ /// an exact version req.
+ pub fn is_locked(&self) -> bool {
+ // Kind of a hack to figure this out, but it works!
+ self.inner.req.to_string().starts_with("=")
+ }
/// Returns false if the dependency is only used to build the local package.
pub fn is_transitive(&self) -> bool {
self.matches_id(sum.package_id())
}
+ /// Returns true if the package (`sum`) can fulfill this dependency request.
+ pub fn matches_ignoring_source(&self, sum: &Summary) -> bool {
+ self.name() == sum.package_id().name() &&
+ self.version_req().matches(sum.package_id().version())
+ }
+
/// Returns true if the package (`id`) can fulfill this dependency request.
pub fn matches_id(&self, id: &PackageId) -> bool {
self.inner.name == id.name() &&
use semver::Version;
use serde::ser;
+use url::Url;
use core::{Dependency, PackageId, Summary, SourceId, PackageIdSpec};
use core::WorkspaceConfig;
profiles: Profiles,
publish: bool,
replace: Vec<(PackageIdSpec, Dependency)>,
+ patch: HashMap<Url, Vec<Dependency>>,
workspace: WorkspaceConfig,
original: Rc<TomlManifest>,
}
#[derive(Clone, Debug)]
pub struct VirtualManifest {
replace: Vec<(PackageIdSpec, Dependency)>,
+ patch: HashMap<Url, Vec<Dependency>>,
workspace: WorkspaceConfig,
profiles: Profiles,
}
profiles: Profiles,
publish: bool,
replace: Vec<(PackageIdSpec, Dependency)>,
+ patch: HashMap<Url, Vec<Dependency>>,
workspace: WorkspaceConfig,
original: Rc<TomlManifest>) -> Manifest {
Manifest {
profiles: profiles,
publish: publish,
replace: replace,
+ patch: patch,
workspace: workspace,
original: original,
}
pub fn publish(&self) -> bool { self.publish }
pub fn replace(&self) -> &[(PackageIdSpec, Dependency)] { &self.replace }
pub fn original(&self) -> &TomlManifest { &self.original }
+ pub fn patch(&self) -> &HashMap<Url, Vec<Dependency>> { &self.patch }
pub fn links(&self) -> Option<&str> {
self.links.as_ref().map(|s| &s[..])
}
impl VirtualManifest {
pub fn new(replace: Vec<(PackageIdSpec, Dependency)>,
+ patch: HashMap<Url, Vec<Dependency>>,
workspace: WorkspaceConfig,
profiles: Profiles) -> VirtualManifest {
VirtualManifest {
replace: replace,
+ patch: patch,
workspace: workspace,
profiles: profiles,
}
&self.replace
}
+ pub fn patch(&self) -> &HashMap<Url, Vec<Dependency>> {
+ &self.patch
+ }
+
pub fn workspace_config(&self) -> &WorkspaceConfig {
&self.workspace
}
use std::collections::HashMap;
+use semver::VersionReq;
+use url::Url;
+
use core::{Source, SourceId, SourceMap, Summary, Dependency, PackageId};
use core::PackageSet;
use util::{Config, profile};
locked: LockedMap,
source_config: SourceConfigMap<'cfg>,
+ patches: HashMap<Url, Vec<Summary>>,
}
type LockedMap = HashMap<SourceId, HashMap<String, Vec<(PackageId, Vec<PackageId>)>>>;
overrides: Vec::new(),
source_config: source_config,
locked: HashMap::new(),
+ patches: HashMap::new(),
})
}
sub_vec.push((id, deps));
}
+ pub fn patch(&mut self, url: &Url, deps: &[Dependency]) -> CargoResult<()> {
+ let deps = deps.iter().map(|dep| {
+ let mut summaries = self.query_vec(dep)?.into_iter();
+ let summary = match summaries.next() {
+ Some(summary) => summary,
+ None => {
+ bail!("patch for `{}` in `{}` did not resolve to any crates",
+ dep.name(), url)
+ }
+ };
+ if summaries.next().is_some() {
+ bail!("patch for `{}` in `{}` resolved to more than one candidate",
+ dep.name(), url)
+ }
+ if summary.package_id().source_id().url() == url {
+ bail!("patch for `{}` in `{}` points to the same source, but \
+ patches must point to different sources",
+ dep.name(), url);
+ }
+ Ok(summary)
+ }).collect::<CargoResult<Vec<_>>>().chain_err(|| {
+ format!("failed to resolve patches for `{}`", url)
+ })?;
+
+ self.patches.insert(url.clone(), deps);
+
+ Ok(())
+ }
+
+ pub fn patches(&self) -> &HashMap<Url, Vec<Summary>> {
+ &self.patches
+ }
+
fn load(&mut self, source_id: &SourceId, kind: Kind) -> CargoResult<()> {
(|| {
let source = self.source_config.load(source_id)?;
/// possible. If we're unable to map a dependency though, we just pass it on
/// through.
pub fn lock(&self, summary: Summary) -> Summary {
- lock(&self.locked, summary)
+ lock(&self.locked, &self.patches, summary)
}
fn warn_bad_override(&self,
fn query(&mut self,
dep: &Dependency,
f: &mut FnMut(Summary)) -> CargoResult<()> {
- // Ensure the requested source_id is loaded
- self.ensure_loaded(dep.source_id(), Kind::Normal).chain_err(|| {
- format!("failed to load source for a dependency \
- on `{}`", dep.name())
- })?;
-
-
let (override_summary, n, to_warn) = {
// Look for an override and get ready to query the real source.
let override_summary = self.query_overrides(&dep)?;
- let source = self.sources.get_mut(dep.source_id());
- match (override_summary, source) {
- (Some(_), None) => bail!("override found but no real ones"),
- (None, None) => return Ok(()),
-
- // If we don't have an override then we just ship everything
- // upstairs after locking the summary
- (None, Some(source)) => {
- let locked = &self.locked;
- return source.query(dep, &mut |summary| f(lock(locked, summary)))
+
+ // Next up on our list of candidates is to check the `[patch]`
+ // section of the manifest. Here we look through all patches
+ // relevant to the source that `dep` points to, and then we match
+ // name/version. Note that we don't use `dep.matches(..)` because
+ // the patches, by definition, come from a different source.
+ // This means that `dep.matches(..)` will always return false, when
+ // what we really care about is the name/version match.
+ let mut patches = Vec::<Summary>::new();
+ if let Some(extra) = self.patches.get(dep.source_id().url()) {
+ patches.extend(extra.iter().filter(|s| {
+ dep.matches_ignoring_source(s)
+ }).cloned());
+ }
+
+ // A crucial feature of the `[patch]` feature is that we *don't*
+ // query the actual registry if we have a "locked" dependency. A
+ // locked dep basically just means a version constraint of `=a.b.c`,
+ // and because patches take priority over the actual source then if
+ // we have a candidate we're done.
+ if patches.len() == 1 && dep.is_locked() {
+ let patch = patches.remove(0);
+ match override_summary {
+ Some(summary) => (summary, 1, Some(patch)),
+ None => {
+ f(patch);
+ return Ok(())
+ }
}
+ } else {
+ if patches.len() > 0 {
+ debug!("found {} patches with an unlocked dep, \
+ looking at sources", patches.len());
+ }
+
+ // Ensure the requested source_id is loaded
+ self.ensure_loaded(dep.source_id(), Kind::Normal).chain_err(|| {
+ format!("failed to load source for a dependency \
+ on `{}`", dep.name())
+ })?;
+
+ let source = self.sources.get_mut(dep.source_id());
+ match (override_summary, source) {
+ (Some(_), None) => bail!("override found but no real ones"),
+ (None, None) => return Ok(()),
+
+ // If we don't have an override then we just ship
+ // everything upstairs after locking the summary
+ (None, Some(source)) => {
+ for patch in patches.iter() {
+ f(patch.clone());
+ }
+
+ // Our sources shouldn't ever come back to us with two
+ // summaries that have the same version. We could,
+ // however, have an `[patch]` section which is in use
+ // to override a version in the registry. This means
+ // that if our `summary` in this loop has the same
+ // version as something in `patches` that we've
+ // already selected, then we skip this `summary`.
+ let locked = &self.locked;
+ let all_patches = &self.patches;
+ return source.query(dep, &mut |summary| {
+ for patch in patches.iter() {
+ let patch = patch.package_id().version();
+ if summary.package_id().version() == patch {
+ return
+ }
+ }
+ f(lock(locked, all_patches, summary))
+ })
+ }
- // If we have an override summary then we query the source to sanity
- // check its results. We don't actually use any of the summaries it
- // gives us though.
- (Some(override_summary), Some(source)) => {
- let mut n = 0;
- let mut to_warn = None;
- source.query(dep, &mut |summary| {
- n += 1;
- to_warn = Some(summary);
- })?;
- (override_summary, n, to_warn)
+ // If we have an override summary then we query the source
+ // to sanity check its results. We don't actually use any of
+ // the summaries it gives us though.
+ (Some(override_summary), Some(source)) => {
+ if patches.len() > 0 {
+ bail!("found patches and a path override")
+ }
+ let mut n = 0;
+ let mut to_warn = None;
+ source.query(dep, &mut |summary| {
+ n += 1;
+ to_warn = Some(summary);
+ })?;
+ (override_summary, n, to_warn)
+ }
}
}
};
}
}
-fn lock(locked: &LockedMap, summary: Summary) -> Summary {
+fn lock(locked: &LockedMap,
+ patches: &HashMap<Url, Vec<Summary>>,
+ summary: Summary) -> Summary {
let pair = locked.get(summary.source_id()).and_then(|map| {
map.get(summary.name())
}).and_then(|vec| {
Some(&(ref precise, _)) => summary.override_id(precise.clone()),
None => summary,
};
- summary.map_dependencies(|mut dep| {
+ summary.map_dependencies(|dep| {
trace!("\t{}/{}/{}", dep.name(), dep.version_req(),
dep.source_id());
let locked = locked_deps.iter().find(|id| dep.matches_id(id));
if let Some(locked) = locked {
trace!("\tfirst hit on {}", locked);
+ let mut dep = dep.clone();
dep.lock_to(locked);
return dep
}
}).and_then(|vec| {
vec.iter().find(|&&(ref id, _)| dep.matches_id(id))
});
- match v {
- Some(&(ref id, _)) => {
- trace!("\tsecond hit on {}", id);
- dep.lock_to(id);
+ if let Some(&(ref id, _)) = v {
+ trace!("\tsecond hit on {}", id);
+ let mut dep = dep.clone();
+ dep.lock_to(id);
+ return dep
+ }
+
+ // Finally we check to see if any registered patches correspond to
+ // this dependency.
+ let v = patches.get(dep.source_id().url()).map(|vec| {
+ let dep2 = dep.clone();
+ let mut iter = vec.iter().filter(move |s| {
+ dep2.name() == s.package_id().name() &&
+ dep2.version_req().matches(s.package_id().version())
+ });
+ (iter.next(), iter)
+ });
+ if let Some((Some(summary), mut remaining)) = v {
+ assert!(remaining.next().is_none());
+ let patch_source = summary.package_id().source_id();
+ let patch_locked = locked.get(patch_source).and_then(|m| {
+ m.get(summary.package_id().name())
+ }).map(|list| {
+ list.iter().any(|&(ref id, _)| id == summary.package_id())
+ }).unwrap_or(false);
+
+ if patch_locked {
+ trace!("\tthird hit on {}", summary.package_id());
+ let req = VersionReq::exact(summary.package_id().version());
+ let mut dep = dep.clone();
+ dep.set_version_req(req);
return dep
}
- None => {
- trace!("\tremaining unlocked");
- dep
- }
}
+
+ trace!("\tnope, unlocked");
+ return dep
})
}
/// `root` is optional to allow forward compatibility.
root: Option<EncodableDependency>,
metadata: Option<Metadata>,
+
+ #[serde(default, skip_serializing_if = "Patch::is_empty")]
+ patch: Patch,
+}
+
+#[derive(Serialize, Deserialize, Debug, Default)]
+struct Patch {
+ unused: Vec<EncodableDependency>,
}
pub type Metadata = BTreeMap<String, String>;
metadata.remove(&k);
}
+ let mut unused_patches = Vec::new();
+ for pkg in self.patch.unused {
+ let id = match pkg.source.as_ref().or(path_deps.get(&pkg.name)) {
+ Some(src) => PackageId::new(&pkg.name, &pkg.version, src)?,
+ None => continue,
+ };
+ unused_patches.push(id);
+ }
+
Ok(Resolve {
graph: g,
empty_features: HashSet::new(),
replacements: replacements,
checksums: checksums,
metadata: metadata,
+ unused_patches: unused_patches,
})
}
}
config: &Config,
ret: &mut HashMap<String, SourceId>,
visited: &mut HashSet<SourceId>) {
- let replace = pkg.manifest().replace();
+ let replace = pkg.manifest().replace().iter().map(|p| &p.1);
+ let patch = pkg.manifest().patch().values().flat_map(|v| v);
let deps = pkg.dependencies()
.iter()
- .chain(replace.iter().map(|p| &p.1))
+ .chain(replace)
+ .chain(patch)
.map(|d| d.source_id())
.filter(|id| !visited.contains(id) && id.is_path())
.filter_map(|id| id.url().to_file_path().ok())
}
}
+impl Patch {
+ fn is_empty(&self) -> bool {
+ self.unused.is_empty()
+ }
+}
+
#[derive(Serialize, Deserialize, Debug, PartialOrd, Ord, PartialEq, Eq)]
pub struct EncodableDependency {
name: String,
Some(root) if self.use_root_key => Some(encodable_resolve_node(&root, self.resolve)),
_ => None,
};
+
+ let patch = Patch {
+ unused: self.resolve.unused_patches().iter().map(|id| {
+ EncodableDependency {
+ name: id.name().to_string(),
+ version: id.version().to_string(),
+ source: encode_source(id.source_id()),
+ dependencies: None,
+ replace: None,
+ }
+ }).collect(),
+ };
EncodableResolve {
package: Some(encodable),
root: root,
metadata: metadata,
+ patch: patch,
}.serialize(s)
}
}
}
};
- let source = if id.source_id().is_path() {
- None
- } else {
- Some(id.source_id().clone())
- };
-
EncodableDependency {
name: id.name().to_string(),
version: id.version().to_string(),
- source: source,
+ source: encode_source(id.source_id()),
dependencies: deps,
replace: replace,
}
}
fn encodable_package_id(id: &PackageId) -> EncodablePackageId {
- let source = if id.source_id().is_path() {
- None
- } else {
- Some(id.source_id().with_precise(None))
- };
EncodablePackageId {
name: id.name().to_string(),
version: id.version().to_string(),
- source: source,
+ source: encode_source(id.source_id()).map(|s| s.with_precise(None)),
+ }
+}
+
+fn encode_source(id: &SourceId) -> Option<SourceId> {
+ if id.is_path() {
+ None
+ } else {
+ Some(id.clone())
}
}
use std::rc::Rc;
use semver;
+use url::Url;
use core::{PackageId, Registry, SourceId, Summary, Dependency};
use core::PackageIdSpec;
features: HashMap<PackageId, HashSet<String>>,
checksums: HashMap<PackageId, Option<String>>,
metadata: Metadata,
+ unused_patches: Vec<PackageId>,
}
pub struct Deps<'a> {
}
impl Resolve {
+ pub fn register_used_patches(&mut self,
+ patches: &HashMap<Url, Vec<Summary>>) {
+ for summary in patches.values().flat_map(|v| v) {
+ if self.iter().any(|id| id == summary.package_id()) {
+ continue
+ }
+ self.unused_patches.push(summary.package_id().clone());
+ }
+ }
+
pub fn merge_from(&mut self, previous: &Resolve) -> CargoResult<()> {
// Given a previous instance of resolve, it should be forbidden to ever
// have a checksums which *differ*. If the same package id has differing
pub fn query(&self, spec: &str) -> CargoResult<&PackageId> {
PackageIdSpec::query_str(spec, self.iter())
}
+
+ pub fn unused_patches(&self) -> &[PackageId] {
+ &self.unused_patches
+ }
}
impl fmt::Debug for Resolve {
features: cx.resolve_features.iter().map(|(k, v)| {
(k.clone(), v.clone())
}).collect(),
+ unused_patches: Vec::new(),
};
for summary in cx.activations.values()
use std::slice;
use glob::glob;
+use url::Url;
use core::{Package, VirtualManifest, EitherManifest, SourceId};
use core::{PackageIdSpec, Dependency, Profile, Profiles};
}
}
+ /// Returns the root [patch] section of this workspace.
+ ///
+ /// This may be from a virtual crate or an actual crate.
+ pub fn root_patch(&self) -> &HashMap<Url, Vec<Dependency>> {
+ let path = match self.root_manifest {
+ Some(ref p) => p,
+ None => &self.current_manifest,
+ };
+ match *self.packages.get(path) {
+ MaybePackage::Package(ref p) => p.manifest().patch(),
+ MaybePackage::Virtual(ref v) => v.patch(),
+ }
+ }
+
/// Returns an iterator over all packages in this workspace
pub fn members<'a>(&'a self) -> Members<'a, 'cfg> {
Members {
emit_package(dep, &mut out);
}
+ if let Some(patch) = toml.get("patch") {
+ let list = patch["unused"].as_array().unwrap();
+ for entry in list {
+ out.push_str("[[patch.unused]]\n");
+ emit_package(entry.as_table().unwrap(), &mut out);
+ out.push_str("\n");
+ }
+ }
+
if let Some(meta) = toml.get("metadata") {
out.push_str("[metadata]\n");
out.push_str(&meta.to_string());
bail!("some crates cannot be published.\n\
`{}` is marked as unpublishable", pkg.name());
}
+ if pkg.manifest().patch().len() > 0 {
+ bail!("published crates cannot contain [patch] sections");
+ }
let (mut registry, reg_id) = registry(opts.config,
- opts.token.clone(),
- opts.index.clone())?;
+ opts.token.clone(),
+ opts.index.clone())?;
verify_dependencies(pkg, ®_id)?;
// Prepare a tarball, with a non-surpressable warning if metadata
.filter(|s| !s.is_registry()));
}
+ let ref keep = |p: &&'a PackageId| {
+ !to_avoid_sources.contains(&p.source_id()) && match to_avoid {
+ Some(set) => !set.contains(p),
+ None => true,
+ }
+ };
+
// In the case where a previous instance of resolve is available, we
// want to lock as many packages as possible to the previous version
// without disturbing the graph structure. To this end we perform
// still matches the locked version.
if let Some(r) = previous {
trace!("previous: {:?}", r);
- for node in r.iter().filter(|p| keep(p, to_avoid, &to_avoid_sources)) {
+ for node in r.iter().filter(keep) {
let deps = r.deps_not_replaced(node)
- .filter(|p| keep(p, to_avoid, &to_avoid_sources))
+ .filter(keep)
.cloned().collect();
registry.register_lock(node.clone(), deps);
}
}
+ for (url, patches) in ws.root_patch() {
+ let previous = match previous {
+ Some(r) => r,
+ None => {
+ registry.patch(url, patches)?;
+ continue
+ }
+ };
+ let patches = patches.iter().map(|dep| {
+ let unused = previous.unused_patches();
+ let candidates = previous.iter().chain(unused);
+ match candidates.filter(keep).find(|id| dep.matches_id(id)) {
+ Some(id) => {
+ let mut dep = dep.clone();
+ dep.lock_to(id);
+ dep
+ }
+ None => dep.clone(),
+ }
+ }).collect::<Vec<_>>();
+ registry.patch(url, &patches)?;
+ }
+
let mut summaries = Vec::new();
for member in ws.members() {
registry.add_sources(&[member.package_id().source_id().clone()])?;
Some(r) => {
root_replace.iter().map(|&(ref spec, ref dep)| {
for (key, val) in r.replacements().iter() {
- if spec.matches(key) &&
- dep.matches_id(val) &&
- keep(&val, to_avoid, &to_avoid_sources) {
+ if spec.matches(key) && dep.matches_id(val) && keep(&val) {
let mut dep = dep.clone();
dep.lock_to(val);
return (spec.clone(), dep)
None => root_replace.to_vec(),
};
- let mut resolved = resolver::resolve(&summaries, &replace, registry)?;
+ let mut resolved = resolver::resolve(&summaries,
+ &replace,
+ registry)?;
+ resolved.register_used_patches(registry.patches());
if let Some(previous) = previous {
resolved.merge_from(previous)?;
}
return Ok(resolved);
-
- fn keep<'a>(p: &&'a PackageId,
- to_avoid_packages: Option<&HashSet<&'a PackageId>>,
- to_avoid_sources: &HashSet<&'a SourceId>)
- -> bool {
- !to_avoid_sources.contains(&p.source_id()) && match to_avoid_packages {
- Some(set) => !set.contains(p),
- None => true,
- }
- }
}
/// Read the `paths` configuration variable to discover all path overrides that
use std::rc::Rc;
use std::str;
-use toml;
use semver::{self, VersionReq};
use serde::ser;
use serde::de::{self, Deserialize};
use serde_ignored;
+use toml;
+use url::Url;
use core::{SourceId, Profiles, PackageIdSpec, GitReference, WorkspaceConfig};
use core::{Summary, Manifest, Target, Dependency, PackageId};
features: Option<HashMap<String, Vec<String>>>,
target: Option<HashMap<String, TomlPlatform>>,
replace: Option<HashMap<String, TomlDependency>>,
+ patch: Option<HashMap<String, HashMap<String, TomlDependency>>>,
workspace: Option<TomlWorkspace>,
badges: Option<HashMap<String, HashMap<String, String>>>,
}
}).collect()
}),
replace: None,
+ patch: None,
workspace: None,
badges: self.badges.clone(),
};
let mut deps = Vec::new();
let replace;
+ let patch;
{
}
replace = me.replace(&mut cx)?;
+ patch = me.patch(&mut cx)?;
}
{
profiles,
publish,
replace,
+ patch,
workspace_config,
me.clone());
if project.license_file.is_some() && project.license.is_some() {
let mut nested_paths = Vec::new();
let mut warnings = Vec::new();
let mut deps = Vec::new();
- let replace = me.replace(&mut Context {
- pkgid: None,
- deps: &mut deps,
- source_id: source_id,
- nested_paths: &mut nested_paths,
- config: config,
- warnings: &mut warnings,
- platform: None,
- root: root
- })?;
+ let (replace, patch) = {
+ let mut cx = Context {
+ pkgid: None,
+ deps: &mut deps,
+ source_id: source_id,
+ nested_paths: &mut nested_paths,
+ config: config,
+ warnings: &mut warnings,
+ platform: None,
+ root: root
+ };
+ (me.replace(&mut cx)?, me.patch(&mut cx)?)
+ };
let profiles = build_profiles(&me.profile);
let workspace_config = match me.workspace {
Some(ref config) => {
bail!("virtual manifests must be configured with [workspace]");
}
};
- Ok((VirtualManifest::new(replace, workspace_config, profiles), nested_paths))
+ Ok((VirtualManifest::new(replace, patch, workspace_config, profiles), nested_paths))
}
fn replace(&self, cx: &mut Context)
-> CargoResult<Vec<(PackageIdSpec, Dependency)>> {
+ if self.patch.is_some() && self.replace.is_some() {
+ bail!("cannot specify both [replace] and [patch]");
+ }
let mut replace = Vec::new();
for (spec, replacement) in self.replace.iter().flat_map(|x| x) {
let mut spec = PackageIdSpec::parse(spec).chain_err(|| {
Ok(replace)
}
+ fn patch(&self, cx: &mut Context)
+ -> CargoResult<HashMap<Url, Vec<Dependency>>> {
+ let mut patch = HashMap::new();
+ for (url, deps) in self.patch.iter().flat_map(|x| x) {
+ let url = match &url[..] {
+ "crates-io" => CRATES_IO.parse().unwrap(),
+ _ => url.to_url()?,
+ };
+ patch.insert(url, deps.iter().map(|(name, dep)| {
+ dep.to_dependency(name, cx, None)
+ }).collect::<CargoResult<Vec<_>>>()?);
+ }
+ Ok(patch)
+ }
+
fn maybe_custom_build(&self,
build: &Option<StringOrBool>,
package_root: &Path)
root crate's `Cargo.toml`.
* The lock file for all crates in the workspace resides next to the root crate's
`Cargo.toml`.
-* The `[replace]` section in `Cargo.toml` is only recognized at the workspace
- root crate, it's ignored in member crates' manifests.
+* The `[patch]` and `[replace]` sections in `Cargo.toml` are only recognized
+ at the workspace root crate, they are ignored in member crates' manifests.
[RFC 1525]: https://github.com/rust-lang/rfcs/blob/master/text/1525-cargo-workspace.md
You can read more about the different crate types in the
[Rust Reference Manual](https://doc.rust-lang.org/reference/linkage.html)
+# The `[patch]` Section
+
+This section of Cargo.toml can be used to [override dependencies][replace] with
+other copies. The syntax is similar to the `[dependencies]` section:
+
+```toml
+[patch.crates-io]
+foo = { git = 'https://github.com/example/foo' }
+bar = { path = 'my/local/bar' }
+```
+
+The `[patch]` table is made of dependency-like sub-tables. Each key after
+`[patch]` is a URL of the source that's being patched, or `crates-io` if
+you're modifying the https://crates.io registry. In the example above
+`crates-io` could be replaced with a git URL such as
+`https://github.com/rust-lang-nursery/log`.
+
+Each entry in these tables is a normal dependency specification, the same as
+found in the `[dependencies]` section of the manifest. The dependencies listed
+in the `[patch]` section are resolved and used to patch the source at the
+URL specified. The above manifest snippet patches the `crates-io` source (e.g.
+crates.io itself) with the `foo` crate and `bar` crate.
+
+Sources can be patched with versions of crates that do not exist, and they can
+also be patched with versions of crates that already exist. If a source is
+patched with a crate version that already exists in the source, then the
+source's original crate is replaced.
+
+More information about overriding dependencies can be found in the [overriding
+dependencies][replace] section of the documentation and [RFC 1969] for the
+technical specification of this feature. Note that the `[patch]` feature will
+first become available in Rust 1.20, set to be released on 2017-08-31.
+
+[RFC 1969]: https://github.com/rust-lang/rfcs/pull/1969
+[replace]: specifying-dependencies.html#overriding-dependencies
+
# The `[replace]` Section
This section of Cargo.toml can be used to [override dependencies][replace] with
More information about overriding dependencies can be found in the [overriding
dependencies][replace] section of the documentation.
-
-[replace]: specifying-dependencies.html#overriding-dependencies
# Overriding dependencies
-Sometimes you may want to override one of Cargo’s dependencies. For example
-let's say you're working on a project using the
-[`uuid`](https://crates.io/crates/uuid) crate which depends on
-[`rand`](https://crates.io/crates/rand). You've discovered there's a bug in
-`rand`, however, and it's already fixed upstream but hasn't been published yet.
-You'd like to test out the fix, so let's first take a look at what your
-`Cargo.toml` will look like:
+There are a number of methods in Cargo to support overriding dependencies and
+otherwise controlling the dependency graph. These options are typically, though,
+only available at the workspace level and aren't propagated through
+dependencies. In other words, "applications" have the ability to override
+dependencies but "libraries" do not.
+
+The desire to override a dependency or otherwise alter some dependencies can
+arise through a number of scenarios. Most of them, however, boil down to the
+ability to to work with a crate before it's been published to crates.io. For
+example:
+
+* A crate you're working on is also used in a much larger application you're
+ working on, and you'd like to test a bug fix to the library inside of the
+ larger application.
+* An upstream crate you don't work on has a new feature or a bug fix on the
+ master branch of its git repository which you'd like to test out.
+* You're about to publish a new major version of your crate, but you'd like to
+ do integration testing across an entire project to ensure the new major
+ version works.
+* You've submitted a fix to an upstream crate for a bug you found, but you'd
+ like to immediately have your application start depending on the fixed version
+ of the crate to avoid blocking on the bug fix getting merged.
+
+These scenarios are currently all solved with the [`[patch]` manifest
+section][patch-section]. Note that the `[patch]` feature is not yet currently
+stable and will be released on 2017-08-31. Historically some of these scenarios
+have been solved with [the `[replace]` section][replace-section], but we'll
+document the `[patch]` section here.
+
+[patch-section]: manifest.html#the-patch-section
+[replace-section]: manifest.html#the-replace-section
+
+### Testing a bugfix
+
+Let's say you're working with the [`uuid`] crate but while you're working on it
+you discover a bug. You are, however, quite enterprising so you decide to also
+try out to fix the bug! Originally your manifest will look like:
+
+[`uuid`](https://crates.io/crates/uuid)
```toml
[package]
-name = "my-awesome-crate"
-version = "0.2.0"
-authors = ["The Rust Project Developers"]
+name = "my-library"
+version = "0.1.0"
+authors = ["..."]
[dependencies]
-uuid = "0.2"
+uuid = "1.0"
```
-To override the `rand` dependency of `uuid`, we'll leverage the [`[replace]`
-section][replace-section] of `Cargo.toml` by appending this to the end:
+First thing we'll do is to clone the [`uuid` repository][uuid-repository]
+locally via:
-[replace-section]: manifest.html#the-replace-section
+```shell
+$ git clone https://github.com/rust-lang-nursery/uuid
+```
+
+Next we'll edit the manifest of `my-library` to contain:
```toml
-[replace]
-"rand:0.3.14" = { git = 'https://github.com/rust-lang-nursery/rand' }
+[patch.crates-io]
+uuid = { path = "../path/to/uuid" }
```
-This indicates that the version of `rand` we're currently using, 0.3.14, will be
-replaced with the master branch of `rand` on GitHub. Next time when you execute
-`cargo build` Cargo will take care of checking out this repository and hooking
-the `uuid` crate up to the new version.
-
-Note that a restriction of `[replace]`, however, is that the replaced crate must
-not only have the same name but also the same version. This means that if the
-`master` branch of `rand` has migrated to, for example, 0.4.3, you'll need to
-follow a few extra steps to test out the crate:
-
-1. Fork the upstream repository to your account
-2. Create a branch which starts from the 0.3.14 release (likely tagged as
- 0.3.14)
-3. Identify the fix of the bug at hand and cherry-pick it onto your branch
-4. Update `[replace]` to point to your git repository and branch
-
-This technique can also be useful when testing out new features for a
-dependency. Following the workflow above you can have a branch where you add
-features, and then once it's ready to go you can send a PR to the upstream
-repository. While you're waiting for the PR to get merged you can continue to
-work locally with a `[replace]`, and then once the PR is merged and published
-you can remove `[replace]` and use the newly-published version.
-
-Note: The `Cargo.lock` file will list two versions of the replaced crate: one
-for the original crate, and one for the version specified in `[replace]`.
-`cargo build -v` can verify that only one version is used in the build.
+Here we declare that we're *patching* the source `crates-io` with a new
+dependency. This will effectively add the local checked out version of `uuid` to
+the crates.io registry for our local project.
-### Overriding with local dependencies
+Next up we need to ensure that our lock file is updated to use this new version
+of `uuid` so our project uses the locally checked out copy instead of one from
+crates.io. The way `[patch]` works is that it'll load the dependency at
+`../path/to/uuid` and then whenever crates.io is queried for versions of `uuid`
+it'll *also* return the local version.
-Sometimes you're only temporarily working on a crate and you don't want to have
-to modify `Cargo.toml` like with the `[replace]` section above. For this use
-case Cargo offers a much more limited version of overrides called **path
-overrides**.
+This means that the version number of the local checkout is significant and will
+affect whether the patch is used. Our manifest declared `uuid = "1.0"` which
+means we'll only resolve to `>= 1.0.0, < 2.0.0`, and Cargo's greedy resolution
+algorithm also means that we'll resolve to the maximum version within that
+range. Typically this doesn't matter as the version of the git repository will
+already be greater or match the maximum version published on crates.io, but it's
+important to keep this in mind!
-Similar to before, let’s say you’re working on a project,
-[`uuid`](https://crates.io/crates/uuid), which depends on
-[`rand`](https://crates.io/crates/rand). This time you're the one who finds a
-bug in `rand`, and you want to write a patch and be able to test out your patch
-by using your version of `rand` in `uuid`. Here’s what `uuid`’s `Cargo.toml`
-looks like:
+In any case, typically all you need to do now is:
+
+```shell
+$ cargo build
+ Compiling uuid v1.0.0 (file://.../uuid)
+ Compiling my-library v0.1.0 (file://.../my-library)
+ Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
+```
+
+And that's it! You're now building with the local version of `uuid` (note the
+`file://` in the build output). If you don't see the `file://` version getting
+built then you may need to run `cargo update -p uuid --precise $version` where
+`$version` is the version of the locally checked out copy of `uuid`.
+
+Once you've fixed the bug you originally found the next thing you'll want to do
+is to likely submit that as a pull request to the `uuid` crate itself. Once
+you've done this then you can also update the `[patch]` section. The listing
+inside of `[patch]` is just like the `[dependencies]` section, so once your pull
+request is merged you could change your `path` dependency to:
+
+```toml
+[patch.crates-io]
+uuid = { git = 'https://github.com/rust-lang-nursery/uuid' }
+```
+
+[uuid-repository]: https://github.com/rust-lang-nursery/uuid
+
+### Working with an unpublished minor version
+
+Let's now shift gears a bit from bug fixes to adding features. While working on
+`my-library` you discover that a whole new feature is needed in the `uuid`
+crate. You've implemented this feature, tested it locally above with `[patch]`,
+and submitted a pull request. Let's go over how you continue to use and test it
+before it's actually published.
+
+Let's also say that the current version of `uuid` on crates.io is `1.0.0`, but
+since then the master branch of the git repository has updated to `1.0.1`. This
+branch includes your new feature you submitted previously. To use this
+repository we'll edit our `Cargo.toml` to look like
```toml
[package]
-name = "uuid"
-version = "0.2.2"
-authors = ["The Rust Project Developers"]
+name = "my-library"
+version = "0.1.0"
+authors = ["..."]
[dependencies]
-rand = { version = "0.3", optional = true }
+uuid = "1.0.1"
+
+[patch.crates-io]
+uuid = { git = 'https://github.com/rust-lang-nursery/uuid' }
```
-You check out a local copy of `rand`, let’s say in your `~/src` directory:
+Note that our local dependency on `uuid` has been updated to `1.0.1` as it's
+what we'll actually require once the crate is published. This version doesn't
+exist on crates.io, though, so we provide it with the `[patch]` section of the
+manifest.
-```shell
-$ cd ~/src
-$ git clone https://github.com/rust-lang-nursery/rand
+Now when our library is built it'll fetch `uuid` from the git repository and
+resolve to 1.0.1 inside the repository instead of trying to download a version
+from crates.io. Once 1.0.1 is published on crates.io the `[patch]` section can
+be deleted.
+
+It's also worth nothing that `[patch]` applies *transitively*. Let's say you use
+`my-library` in a larger project, such as:
+
+```toml
+[package]
+name = "my-binary"
+version = "0.1.0"
+authors = ["..."]
+
+[dependencies]
+my-library = { git = 'https://example.com/git/my-library' }
+uuid = "1.0"
+
+[patch.crates-io]
+uuid = { git = 'https://github.com/rust-lang-nursery/uuid' }
```
-A path override is communicated to Cargo through the `.cargo/config`
-configuration mechanism. If Cargo finds this configuration when building your
-package, it will use the override on your local machine instead of the source
-specified in your `Cargo.toml`.
+Remember that `[patch]` is only applicable at the *top level* so we consumers of
+`my-library` have to repeat the `[patch]` section if necessary. Here, though,
+the new `uuid` crate applies to *both* our dependency on `uuid` and the
+`my-library -> uuid` dependency. The `uuid` crate will be resolved to one
+version for this entire crate graph, 1.0.1, and it'll be pulled from the git
+repository.
-Cargo looks for a directory named `.cargo` up the directory hierarchy of
-your project. If your project is in `/path/to/project/uuid`,
-it will search for a `.cargo` in:
+### Prepublishing a breaking change
-* `/path/to/project/uuid`
-* `/path/to/project`
-* `/path/to`
-* `/path`
-* `/`
+As a final scenario, let's take a look at working with a new major version of a
+crate, typically accompanied with breaking changes. Sticking with our previous
+crates, this means that we're going to be creating version 2.0.0 of the `uuid`
+crate. After we've submitted all changes upstream we can update our manifest for
+`my-library` to look like:
-This allows you to specify your overrides in a parent directory that
-includes commonly used packages that you work on locally and share them
-with all projects.
+```toml
+[dependencies]
+uuid = "2.0"
-To specify overrides, create a `.cargo/config` file in some ancestor of
-your project’s directory (common places to put it is in the root of
-your code directory or in your home directory).
+[patch.crates-io]
+uuid = { git = "https://github.com/rust-lang-nursery/uuid", branch = "2.0.0" }
+```
-Inside that file, put this:
+And that's it! Like with the previous example the 2.0.0 version doesn't actually
+exist on crates.io but we can still put it in through a git dependency through
+the usage of the `[patch]` section. As a thought exercise let's take another
+look at the `my-binary` manifest from above again as well:
```toml
-paths = ["/path/to/project/rand"]
+[package]
+name = "my-binary"
+version = "0.1.0"
+authors = ["..."]
+
+[dependencies]
+my-library = { git = 'https://example.com/git/my-library' }
+uuid = "1.0"
+
+[patch.crates-io]
+uuid = { git = 'https://github.com/rust-lang-nursery/uuid', version = '2.0.0' }
+```
+
+Note that this will actually resolve to two versions of the `uuid` crate. The
+`my-binary` crate will continue to use the 1.x.y series of the `uuid` crate but
+the `my-library` crate will use the 2.0.0 version of `uuid`. This will allow you
+to gradually roll out breaking changes to a crate through a dependency graph
+without being force to update everything all at once.
+
+### Overriding with local dependencies
+
+Sometimes you're only temporarily working on a crate and you don't want to have
+to modify `Cargo.toml` like with the `[patch]` section above. For this use
+case Cargo offers a much more limited version of overrides called **path
+overrides**.
+
+Path overrides are specified through `.cargo/config` instead of `Cargo.toml`,
+and you can find [more documentation about this configuration][config-docs].
+Inside of `.cargo/config` you'll specify a key called `paths`:
+
+[config-docs]: config.html
+
+```toml
+paths = ["/path/to/uuid"]
```
This array should be filled with directories that contain a `Cargo.toml`. In
-this instance, we’re just adding `rand`, so it will be the only one that’s
-overridden. This path must be an absolute path.
+this instance, we’re just adding `uuid`, so it will be the only one that’s
+overridden. This path can be either absolute or relative to the directory that
+contains the `.cargo` folder.
-Path overrides are more restricted than the `[replace]` section, however, in
+Path overrides are more restricted than the `[patch]` section, however, in
that they cannot change the structure of the dependency graph. When a
-replacement is applied it must be the case that the previous set of dependencies
-all match exactly and can be used for the replacement. For example this means
-that path overrides cannot be used to test out adding a dependency to a crate,
-instead `[replace]` must be used in that situation.
+path replacement is used then the previous set of dependencies
+must all match exactly to the new `Cargo.toml` specification. For example this
+means that path overrides cannot be used to test out adding a dependency to a
+crate, instead `[patch]` must be used in that situation. As a result usage of a
+path override is typically isolated to quick bug fixes rather than larger
+changes.
Note: using a local configuration to override paths will only work for crates
that have been published to [crates.io]. You cannot use this feature to tell
Cargo how to find local unpublished crates.
-More information about local configuration can be found in the [configuration
-documentation](config.html).
-
# Platform specific dependencies
pub fn file<B: AsRef<Path>>(mut self, path: B,
body: &str) -> ProjectBuilder {
- self.files.push(FileBuilder::new(self.root.join(path), body));
+ self._file(path.as_ref(), body);
self
}
+ fn _file(&mut self, path: &Path, body: &str) {
+ self.files.push(FileBuilder::new(self.root.join(path), body));
+ }
+
pub fn change_file(&self, path: &str, body: &str) {
assert!(self.is_build.get());
FileBuilder::new(self.root.join(path), body).mk()
}
pub fn with_stderr<S: ToString>(mut self, expected: S) -> Execs {
- self.expect_stderr = Some(expected.to_string());
+ self._with_stderr(&expected);
self
}
+ fn _with_stderr(&mut self, expected: &ToString) {
+ self.expect_stderr = Some(expected.to_string());
+ }
+
pub fn with_status(mut self, expected: i32) -> Execs {
self.expect_exit_code = Some(expected);
self
--- /dev/null
+#[macro_use]
+extern crate cargotest;
+extern crate hamcrest;
+extern crate toml;
+
+use std::fs::{self, File};
+use std::io::{Read, Write};
+
+use cargotest::support::git;
+use cargotest::support::paths;
+use cargotest::support::registry::Package;
+use cargotest::support::{execs, project};
+use hamcrest::assert_that;
+
+#[test]
+fn replace() {
+ Package::new("foo", "0.1.0").publish();
+ Package::new("deep-foo", "0.1.0")
+ .file("src/lib.rs", r#"
+ extern crate foo;
+ pub fn deep() {
+ foo::foo();
+ }
+ "#)
+ .dep("foo", "0.1.0")
+ .publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ deep-foo = "0.1.0"
+
+ [patch.crates-io]
+ foo = { path = "foo" }
+ "#)
+ .file("src/lib.rs", "
+ extern crate foo;
+ extern crate deep_foo;
+ pub fn bar() {
+ foo::foo();
+ deep_foo::deep();
+ }
+ ")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#"
+ pub fn foo() {}
+ "#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[DOWNLOADING] deep-foo v0.1.0 ([..])
+[COMPILING] foo v0.1.0 (file://[..])
+[COMPILING] deep-foo v0.1.0
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+
+ assert_that(p.cargo("build"),//.env("RUST_LOG", "trace"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+}
+
+#[test]
+fn nonexistent() {
+ Package::new("baz", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+
+ [patch.crates-io]
+ foo = { path = "foo" }
+ "#)
+ .file("src/lib.rs", "
+ extern crate foo;
+ pub fn bar() {
+ foo::foo();
+ }
+ ")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#"
+ pub fn foo() {}
+ "#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[COMPILING] foo v0.1.0 (file://[..])
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+}
+
+#[test]
+fn patch_git() {
+ let foo = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "");
+ foo.build();
+
+ let p = project("bar")
+ .file("Cargo.toml", &format!(r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = {{ git = '{}' }}
+
+ [patch.'{0}']
+ foo = {{ path = "foo" }}
+ "#, foo.url()))
+ .file("src/lib.rs", "
+ extern crate foo;
+ pub fn bar() {
+ foo::foo();
+ }
+ ")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#"
+ pub fn foo() {}
+ "#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] git repository `file://[..]`
+[COMPILING] foo v0.1.0 (file://[..])
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+}
+
+#[test]
+fn patch_to_git() {
+ let foo = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "pub fn foo() {}");
+ foo.build();
+
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", &format!(r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1"
+
+ [patch.crates-io]
+ foo = {{ git = '{}' }}
+ "#, foo.url()))
+ .file("src/lib.rs", "
+ extern crate foo;
+ pub fn bar() {
+ foo::foo();
+ }
+ ");
+
+ assert_that(p.cargo_process("build"),//.env("RUST_LOG", "cargo=trace"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] git repository `file://[..]`
+[UPDATING] registry `file://[..]`
+[COMPILING] foo v0.1.0 (file://[..])
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+}
+
+#[test]
+fn unused() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+
+ [patch.crates-io]
+ foo = { path = "foo" }
+ "#)
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.2.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#"
+ not rust code
+ "#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[DOWNLOADING] foo v0.1.0 [..]
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+
+ // unused patch should be in the lock file
+ let mut lock = String::new();
+ File::open(p.root().join("Cargo.lock")).unwrap()
+ .read_to_string(&mut lock).unwrap();
+ let toml: toml::Value = toml::from_str(&lock).unwrap();
+ assert_eq!(toml["patch"]["unused"].as_array().unwrap().len(), 1);
+ assert_eq!(toml["patch"]["unused"][0]["name"].as_str(), Some("foo"));
+ assert_eq!(toml["patch"]["unused"][0]["version"].as_str(), Some("0.2.0"));
+}
+
+#[test]
+fn unused_git() {
+ Package::new("foo", "0.1.0").publish();
+
+ let foo = git::repo(&paths::root().join("override"))
+ .file("Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.2.0"
+ authors = []
+ "#)
+ .file("src/lib.rs", "");
+ foo.build();
+
+ let p = project("bar")
+ .file("Cargo.toml", &format!(r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1"
+
+ [patch.crates-io]
+ foo = {{ git = '{}' }}
+ "#, foo.url()))
+ .file("src/lib.rs", "");
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] git repository `file://[..]`
+[UPDATING] registry `file://[..]`
+[DOWNLOADING] foo v0.1.0 [..]
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+}
+
+#[test]
+fn add_patch() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[DOWNLOADING] foo v0.1.0 [..]
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+
+ t!(t!(File::create(p.root().join("Cargo.toml"))).write_all(br#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ "#));
+
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("\
+[COMPILING] foo v0.1.0 (file://[..])
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+}
+
+#[test]
+fn add_ignored_patch() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+ "#)
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.1"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[DOWNLOADING] foo v0.1.0 [..]
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+
+ t!(t!(File::create(p.root().join("Cargo.toml"))).write_all(br#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ "#));
+
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("\
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("[FINISHED] [..]"));
+}
+
+#[test]
+fn new_minor() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1.0"
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ "#)
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.1"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[COMPILING] foo v0.1.1 [..]
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+}
+
+#[test]
+fn transitive_new_minor() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ subdir = { path = 'subdir' }
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ "#)
+ .file("src/lib.rs", "")
+ .file("subdir/Cargo.toml", r#"
+ [package]
+ name = "subdir"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = '0.1.0'
+ "#)
+ .file("subdir/src/lib.rs", r#""#)
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.1"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[COMPILING] foo v0.1.1 [..]
+[COMPILING] subdir v0.1.0 [..]
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+}
+
+#[test]
+fn new_major() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.2.0"
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ "#)
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.2.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[COMPILING] foo v0.2.0 [..]
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+
+ Package::new("foo", "0.2.0").publish();
+ assert_that(p.cargo("update"),
+ execs().with_status(0));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("\
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+
+ t!(t!(File::create(p.root().join("Cargo.toml"))).write_all(br#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.2.0"
+ "#));
+ assert_that(p.cargo("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[DOWNLOADING] foo v0.2.0 [..]
+[COMPILING] foo v0.2.0
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+}
+
+#[test]
+fn transitive_new_major() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ subdir = { path = 'subdir' }
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ "#)
+ .file("src/lib.rs", "")
+ .file("subdir/Cargo.toml", r#"
+ [package]
+ name = "subdir"
+ version = "0.1.0"
+ authors = []
+
+ [dependencies]
+ foo = '0.2.0'
+ "#)
+ .file("subdir/src/lib.rs", r#""#)
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.2.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(0).with_stderr("\
+[UPDATING] registry `file://[..]`
+[COMPILING] foo v0.2.0 [..]
+[COMPILING] subdir v0.1.0 [..]
+[COMPILING] bar v0.0.1 (file://[..])
+[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
+"));
+}
+
+#[test]
+fn remove_patch() {
+ Package::new("foo", "0.1.0").publish();
+ Package::new("bar", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1"
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ bar = { path = 'bar' }
+ "#)
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#)
+ .file("bar/Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("bar/src/lib.rs", r#""#);
+
+ // Generate a lock file where `bar` is unused
+ assert_that(p.cargo_process("build"), execs().with_status(0));
+ let mut lock_file1 = String::new();
+ File::open(p.root().join("Cargo.lock")).unwrap()
+ .read_to_string(&mut lock_file1).unwrap();
+
+ // Remove `bar` and generate a new lock file form the old one
+ File::create(p.root().join("Cargo.toml")).unwrap().write_all(r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [dependencies]
+ foo = "0.1"
+
+ [patch.crates-io]
+ foo = { path = 'foo' }
+ "#.as_bytes()).unwrap();
+ assert_that(p.cargo("build"), execs().with_status(0));
+ let mut lock_file2 = String::new();
+ File::open(p.root().join("Cargo.lock")).unwrap()
+ .read_to_string(&mut lock_file2).unwrap();
+
+ // Remove the lock file and build from scratch
+ fs::remove_file(p.root().join("Cargo.lock")).unwrap();
+ assert_that(p.cargo("build"), execs().with_status(0));
+ let mut lock_file3 = String::new();
+ File::open(p.root().join("Cargo.lock")).unwrap()
+ .read_to_string(&mut lock_file3).unwrap();
+
+ assert!(lock_file1.contains("bar"));
+ assert_eq!(lock_file2, lock_file3);
+ assert!(lock_file1 != lock_file2);
+}
+
+#[test]
+fn non_crates_io() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [patch.some-other-source]
+ foo = { path = 'foo' }
+ "#)
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(101)
+ .with_stderr("\
+error: failed to parse manifest at `[..]`
+
+Caused by:
+ invalid url `some-other-source`: relative URL without a base
+"));
+}
+
+#[test]
+fn replace_with_crates_io() {
+ Package::new("foo", "0.1.0").publish();
+
+ let p = project("bar")
+ .file("Cargo.toml", r#"
+ [package]
+ name = "bar"
+ version = "0.0.1"
+ authors = []
+
+ [patch.crates-io]
+ foo = "0.1"
+ "#)
+ .file("src/lib.rs", "")
+ .file("foo/Cargo.toml", r#"
+ [package]
+ name = "foo"
+ version = "0.1.0"
+ authors = []
+ "#)
+ .file("foo/src/lib.rs", r#""#);
+
+ assert_that(p.cargo_process("build"),
+ execs().with_status(101)
+ .with_stderr("\
+[UPDATING] [..]
+error: failed to resolve patches for `[..]`
+
+Caused by:
+ patch for `foo` in `[..]` points to the same source, but patches must point \
+ to different sources
+"));
+}